
#include "../../Common/D3DApp.h"
#include "../../Common/MathHelper.h"
#include "../../Common/UploadBuffer.h"
#include "../../Common/GeometryGenerator.h"
#include "../../Common/DDSTextureLoader.h"
#include "../../Common/RenderTarget.h"
#include "FrameResource.h"
#include "../../Common/Singleton.h"

#include "CSFeatures/BlurFilter.h"
#include "CSFeatures/GPUWaves.h"
#include "CSFeatures/VecAdd.h"
#include "Pass/AlphaTestPass.h"
#include "Pass/MirrorPass.h"
#include "Pass/BasePass.h"
#include "Pass/BasePass.h"
#include "Pass/ComposePass.h"
#include "Pass/ReflectionPass.h"
#include "Pass/TessellationPass.h"
#include "Pass/TransparentPass.h"
#include "Pass/TreeSpritesPass.h"
#include "Pass/WavesRenderPass.h"
#include "ResourceManager/Dx2GeometryManager.h"
#include "ResourceManager/Dx2MaterialManager.h"
#include "ResourceManager/Dx2RootSignatureManager.h"
#include "ResourceManager/Dx2ShaderAndLayoutManager.h"
#include "ResourceManager/Dx2TextureManager.h"

using Microsoft::WRL::ComPtr;
using namespace DirectX;
using namespace DirectX::PackedVector;

const int gNumFrameResources = 3;

class ShapesApp : public  D3DApp
{
public:
	ShapesApp(HINSTANCE hInstance);
	ShapesApp(const ShapesApp& rhs) = delete;
	ShapesApp& operator=(const ShapesApp& rhs) = delete;
	~ShapesApp();

	virtual bool Initialize() override;
	
private:
	virtual void CreateRtvAndDsvDescriptorHeaps() override;
	virtual void OnResize() override;
	virtual void Update(const GameTimer& gt) override;
	virtual void Draw(const GameTimer& gt) override;

	virtual void OnMouseDown(WPARAM btnState, int x, int y) override;
	virtual void OnMouseUp(WPARAM btnState, int x, int y) override;
	virtual void OnMouseMove(WPARAM btnState, int x, int y) override;

	void OnKeyboardInput(const GameTimer& gt);
	void UpdateCamera(const GameTimer& gt);
	void AnimateMaterials(const GameTimer& gt);
	void UpdateObjectCBs(const GameTimer& gt);
	void UpdateMaterialCBs(const GameTimer& gt);
	void UpdateMainPassCB(const GameTimer& gt);
	void UpdateReflectedPassCB(const GameTimer& gt);
	void UpdateWavesGpu(const GameTimer& gt);

	void LoadTextures();
	void BuildGeometry();

	void BuildRootSignature();
	void BuildShadersAndInputLayout();
	void BuildMaterials();
	
	void BuildDescriptorHeaps();
	
	void BuildCSPSOs();
	void BuildFrameResources();
	
	void CreatePasses();
	void InitialPasses();
	
private:
	std::vector<std::unique_ptr<FrameResource>> mFrameResources;
	FrameResource* mCurrFrameResource = nullptr;
	int mCurrFrameResourceIndex = 0;
	
	 UINT mCbvSrvDescriptorSize = 0;
	
	//srv description
	Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mSrvDescriptorHeap = nullptr;

	//pass
	std::unordered_map<std::string, Microsoft::WRL::ComPtr<ID3D12PipelineState>> mPSOs;
	std::unordered_map<std::string,std::shared_ptr<Pass>> mAllPasses;
	std::unordered_map<std::string,std::shared_ptr<Pass>> mMainPasses;
	std::unordered_map<std::string,std::shared_ptr<Pass>> mPostPasses;
	
	// List of all the render items.
	std::vector<std::unique_ptr<RenderItem>> mAllRenderItems;
	
	//render item
	RenderItem* mSkullRitem = nullptr;
	RenderItem* mReflectedSkullRitem = nullptr;
	RenderItem* mTessellationRitem = nullptr;
	
	std::shared_ptr<GPUWaves> mWaves;
	std::unique_ptr<VecAdd> mVecAdd;
	std::unique_ptr<BlurFilter> mBlurFilter;
	
	std::unique_ptr<RenderTarget> mOffscreenRT;

    PassConstants mMainPassCB;
	PassConstants mReflectedPassCB;

	XMFLOAT3 mSkullTranslation = { 0.0f, 1.0f, -5.0f };
	XMFLOAT3 mTessellationTranslation  = {-20.0f,3.0f,0.0f};

	XMFLOAT3 mEyePos = { 0.0f, 0.0f, 0.0f };
	XMFLOAT4X4 mView = MathHelper::Identity4x4();
	XMFLOAT4X4 mProj = MathHelper::Identity4x4();

    float mTheta = 1.5f*XM_PI;
    float mPhi = XM_PIDIV2 - 0.1f;
    float mRadius = 50.0f;

	float mSunTheta = 1.25f*XM_PI;
	float mSunPhi = XM_PIDIV4;
	POINT mLastMousePos;

	bool HAS_FOG_EFFECT = false;
	bool HAS_BLUR_EFFECT = false;
	bool IS_USE_OFFSCEEN_RENDERTARGET = false;
};



int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance,
				   PSTR cmdLine, int showCmd)
{
	
	// Enable run-time memory check for debug builds.
	#if defined(DEBUG) | defined(_DEBUG)
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif	

	try
	{
		ShapesApp theApp(hInstance);

		if (!theApp.Initialize())
			return 0;

		return theApp.Run();
	}
	catch (DxException& e)
	{
		MessageBox(nullptr, e.ToString().c_str(), L"HR Failed", MB_OK);
		return 0;
	}
}

ShapesApp::ShapesApp(HINSTANCE hInstance)
	: D3DApp(hInstance)
{
}

ShapesApp::~ShapesApp()
{
	if(md3dDevice != nullptr)
		FlushCommandQueue();
}

bool ShapesApp::Initialize()
{
	if(!D3DApp::Initialize())
		return false;

	ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(),nullptr));

	mCbvSrvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

	mWaves = std::make_unique<GPUWaves>(
		md3dDevice.Get(),
		mCommandList.Get(),
		256, 256, 0.25f, 0.03f, 2.0f, 0.2f);

	mVecAdd = std::make_unique<VecAdd>(
		md3dDevice.Get(),
		mCommandList.Get(),
		32);

	mBlurFilter = std::make_unique<BlurFilter>(
		md3dDevice.Get(),
		mClientWidth,mClientHeight,
		DXGI_FORMAT_R8G8B8A8_UNORM);

	mOffscreenRT = std::make_unique<RenderTarget>(
		md3dDevice.Get(),
		mClientWidth,mClientHeight,
		mBackBufferFormat);
	
	//create resource
	LoadTextures();
	BuildGeometry();
	BuildShadersAndInputLayout();
	BuildMaterials();
	BuildRootSignature();

	//build descriptor
	BuildDescriptorHeaps();

	//build pass
	CreatePasses();
	InitialPasses();
	
	//Frame const resource
	BuildFrameResources();

	//build Compute PSO
	BuildCSPSOs();
	
	ThrowIfFailed(mCommandList->Close());
	ID3D12CommandList* cmdsLists[] = {mCommandList.Get()};
	mCommandQueue->ExecuteCommandLists(_countof(cmdsLists),cmdsLists);
			
	FlushCommandQueue();

	//vec add test
	mVecAdd->DoComputeWork(
		mDirectCmdListAlloc.Get(),
		mCommandList.Get(),
		Singleton<Dx2RootSignatureManager>::Instance()->GetRootSignature(RootSignatureType::VecAndAdd).Get(),
		mPSOs["vecAdd"].Get()
		);

	ThrowIfFailed(mCommandList->Close());
	ID3D12CommandList* cmdsLists2[] = {mCommandList.Get()};
	mCommandQueue->ExecuteCommandLists(_countof(cmdsLists2),cmdsLists2);
		
	FlushCommandQueue();

	mVecAdd->AfterComputeWork();
	
	
	return true;
}

void ShapesApp::CreateRtvAndDsvDescriptorHeaps()
{
	// D3DApp::CreateRtvAndDsvDescriptorHeaps();

	D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc;
	rtvHeapDesc.NumDescriptors = SwapChainBufferCount + 1;
	rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
	rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
	rtvHeapDesc.NodeMask = 0;

	ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
		&rtvHeapDesc, IID_PPV_ARGS(mRtvHeap.GetAddressOf())));

	D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc;
	dsvHeapDesc.NumDescriptors = 1;
	dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
	dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
	dsvHeapDesc.NodeMask = 0;

	ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
		&dsvHeapDesc,IID_PPV_ARGS(mDsvHeap.GetAddressOf())));
}

void ShapesApp::OnResize()
{
	D3DApp::OnResize();

	XMMATRIX P = XMMatrixPerspectiveFovLH(0.25f*MathHelper::Pi, AspectRatio(), 1.0f, 1000.0f);
	XMStoreFloat4x4(&mProj, P);

	if(mBlurFilter != nullptr)
	{
		mBlurFilter->OnResize(mClientWidth,mClientHeight);
	}

	if(mOffscreenRT != nullptr)
	{
		mOffscreenRT->OnResize(mClientWidth,mClientHeight);
	}
}

void ShapesApp::Update(const GameTimer& gt)
{
	OnKeyboardInput(gt);
	UpdateCamera(gt);
	
	mCurrFrameResourceIndex = (mCurrFrameResourceIndex + 1) % gNumFrameResources;
	mCurrFrameResource = mFrameResources[mCurrFrameResourceIndex].get();

	if(mCurrFrameResource->Fence != 0 && mFence->GetCompletedValue() < mCurrFrameResource->Fence)
	{
		HANDLE eventHandle = CreateEventEx(nullptr,FALSE,FALSE,EVENT_ALL_ACCESS);
		ThrowIfFailed(mFence->SetEventOnCompletion(mCurrFrameResource->Fence,eventHandle));
		WaitForSingleObject(eventHandle,INFINITE);
		CloseHandle(eventHandle);
	}

	AnimateMaterials(gt);
	UpdateObjectCBs(gt);
	UpdateMaterialCBs(gt);
	UpdateMainPassCB(gt);
	UpdateReflectedPassCB(gt);
}

void ShapesApp::Draw(const GameTimer& gt)
{
	auto cmdListAlloc = mCurrFrameResource->CmdListAlloc;
	
	ThrowIfFailed(cmdListAlloc->Reset());

	ThrowIfFailed(mCommandList->Reset(cmdListAlloc.Get(),mPSOs["opaque"].Get()));

	UpdateWavesGpu(gt);
	
	//set descriptor heap
	ID3D12DescriptorHeap* descriptorHeaps[] = {mSrvDescriptorHeap.Get()};
	mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps),descriptorHeaps);
	
	mCommandList->RSSetViewports(1,&mScreenViewport);
	mCommandList->RSSetScissorRects(1,&mScissorRect);

	ID3D12Resource* RenderTarget;
	D3D12_CPU_DESCRIPTOR_HANDLE RenderTargetView;
	D3D12_CPU_DESCRIPTOR_HANDLE RenderDepthStencilView;

	{
		RenderTarget = CurrentBackBuffer();
		RenderTargetView = CurrentBackBufferView();
		RenderDepthStencilView = DepthStencilView();

		//transition backbuffer
		auto transitionBackBuffer = CD3DX12_RESOURCE_BARRIER::Transition(RenderTarget,
			D3D12_RESOURCE_STATE_PRESENT,D3D12_RESOURCE_STATE_RENDER_TARGET);
		mCommandList->ResourceBarrier(1,&transitionBackBuffer);
	}
	
	CD3DX12_GPU_DESCRIPTOR_HANDLE gpuHanlder(mSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());
	UINT objCBByteSize =  d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
	UINT matCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(MaterialConstants));
	UINT passCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(PassConstants));
	auto objectCB = mCurrFrameResource->ObjectCB->Resource();
	auto matCB = mCurrFrameResource->MaterialCB->Resource();
	auto passCB  = mCurrFrameResource->PassCB->Resource();
	
	for(auto it = mMainPasses.begin();it != mMainPasses.end();it++)
	{
		auto curPass = it->second;
		curPass->Draw(
			mCommandList.Get(),
			gpuHanlder,
			mCbvSrvDescriptorSize,
			objCBByteSize,matCBByteSize,passCBByteSize,
			objectCB,matCB,passCB,mMainPassCB,
			RenderTarget,
			RenderTargetView,
			RenderDepthStencilView
			);
	}

	//post process
	if(HAS_BLUR_EFFECT)
	{
		mBlurFilter->Excute(mCommandList.Get(),Singleton<Dx2RootSignatureManager>::Instance()->GetRootSignature(RootSignatureType::Blur).Get(),
			mPSOs["horzBlur"].Get(),mPSOs["vertBlur"].Get(),RenderTarget,4);
	}
	

	//general transition
	auto transitionClose = CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),D3D12_RESOURCE_STATE_RENDER_TARGET,D3D12_RESOURCE_STATE_PRESENT);
	mCommandList->ResourceBarrier(1,
		&transitionClose);
		
	ThrowIfFailed(mCommandList->Close());

	ID3D12CommandList* cmdsLists[] = {mCommandList.Get()};
	mCommandQueue->ExecuteCommandLists(_countof(cmdsLists),cmdsLists);

	ThrowIfFailed(mSwapChain->Present(0,0));
	mCurrBackBuffer = (mCurrBackBuffer + 1) % SwapChainBufferCount;
	
 	mCurrFrameResource->Fence = ++mCurrentFence;
	mCommandQueue->Signal(mFence.Get(), mCurrentFence);
}

void ShapesApp::OnMouseDown(WPARAM btnState, int x, int y)
{
	mLastMousePos.x = x;
	mLastMousePos.y = y;

	SetCapture(mhMainWnd);
}

void ShapesApp::OnMouseUp(WPARAM btnState, int x, int y)
{
	ReleaseCapture();
}

void ShapesApp::OnMouseMove(WPARAM btnState, int x, int y)
{
	if((btnState & MK_LBUTTON) != 0)
	{
		float dx = XMConvertToRadians(0.25f * static_cast<float>(x - mLastMousePos.x));
		float dy = XMConvertToRadians(0.25f * static_cast<float>(y - mLastMousePos.y));

		mTheta += dx;
		mPhi += dy;

        // Restrict the angle mPhi.
        mPhi = MathHelper::Clamp(mPhi, 0.1f, MathHelper::Pi - 0.1f);
    }
    else if((btnState & MK_RBUTTON) != 0)
    {
        // Make each pixel correspond to 0.2 unit in the scene.
        float dx = 0.2f*static_cast<float>(x - mLastMousePos.x);
        float dy = 0.2f*static_cast<float>(y - mLastMousePos.y);

        // Update the camera radius based on input.
        mRadius += dx - dy;

        // Restrict the radius.
        mRadius = MathHelper::Clamp(mRadius, 5.0f, 150.0f);
    }

    mLastMousePos.x = x;
    mLastMousePos.y = y;
}

void ShapesApp::OnKeyboardInput(const GameTimer& gt)
{
	const float dt = gt.DeltaTime();
	
	if(GetAsyncKeyState(VK_LEFT) & 0x8000)
		mSunTheta -= 1.0f*dt;
	
	if(GetAsyncKeyState(VK_RIGHT) & 0x8000)
		mSunTheta += 1.0f*dt;
	
	if(GetAsyncKeyState(VK_UP) & 0x8000)
		mSunPhi -= 1.0f*dt;
	
	if(GetAsyncKeyState(VK_DOWN) & 0x8000)
		mSunPhi += 1.0f*dt;
	
	mSunPhi = MathHelper::Clamp(mSunPhi, 0.1f, XM_PIDIV2);

	XMMATRIX skullRotate = XMMatrixRotationY(0.5f * MathHelper::Pi);
	XMMATRIX skullScale = XMMatrixScaling(0.45f,0.45f,0.45f);
	XMMATRIX skullOffset = XMMatrixTranslation(mSkullTranslation.x, mSkullTranslation.y, mSkullTranslation.z);
	XMMATRIX skullWorld = skullRotate * skullScale * skullOffset;
	XMStoreFloat4x4(&mSkullRitem->World,skullWorld);

	XMVECTOR mirrorPlan = XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f);
	XMMATRIX R = XMMatrixReflect(mirrorPlan);
	XMStoreFloat4x4(&mReflectedSkullRitem->World,skullWorld * R);

	XMMATRIX tessellationRotate = XMMatrixRotationY(0.0f);
	XMMATRIX tesselationScale = XMMatrixScaling(1.0f, 1.0f, 1.0f);
	XMMATRIX tesselationOffset = XMMatrixTranslation(mTessellationTranslation.x, mTessellationTranslation.y, mTessellationTranslation.z);
	XMMATRIX tessellationWorld = tessellationRotate * tesselationScale * tesselationOffset;
	XMStoreFloat4x4(&mTessellationRitem->World, tessellationWorld);
	
	mSkullRitem->NumFramesDirty = gNumFrameResources;
	mReflectedSkullRitem->NumFramesDirty = gNumFrameResources;
	mTessellationRitem->NumFramesDirty = gNumFrameResources;
}

void ShapesApp::UpdateCamera(const GameTimer& gt)
{
	mEyePos.x = mRadius * sinf(mPhi) * cosf(mTheta);
	mEyePos.z = mRadius * sinf(mPhi) * sinf(mTheta);
	mEyePos.y = mRadius * cosf(mPhi);

	XMVECTOR pos = XMVectorSet(mEyePos.x,mEyePos.y,mEyePos.z,1.0f);
	XMVECTOR target = XMVectorZero();
	XMVECTOR up = XMVectorSet(0.0f,1.0f,0.0f,0.0f);

	XMMATRIX view =  XMMatrixLookAtLH(pos,target,up);
	XMStoreFloat4x4(&mView,view);
}

void ShapesApp::AnimateMaterials(const GameTimer& gt)
{
	auto waterMat = Singleton<Dx2MaterialManager>::Instance()->GetMaterial("water").get();

	float& tu = waterMat->MatTransform(3, 0);
	float& tv = waterMat->MatTransform(3, 1);

	tu += 0.1f * gt.DeltaTime();
	tv += 0.02f * gt.DeltaTime();

	if(tu >= 1.0f)
		tu -= 1.0f;

	if(tv >= 1.0f)
		tv -= 1.0f;

	waterMat->MatTransform(3, 0) = tu;
	waterMat->MatTransform(3, 1) = tv;

	// Material has changed, so need to update cbuffer.
	waterMat->NumFramesDirty = gNumFrameResources;
}

void ShapesApp::UpdateObjectCBs(const GameTimer& gt)
{
	auto currObjectCB = mCurrFrameResource->ObjectCB.get();
	for(auto& e : mAllRenderItems)
	{
		if(e->NumFramesDirty > 0)
		{
			XMMATRIX world = XMLoadFloat4x4(&e->World);
			XMMATRIX texTransform = XMLoadFloat4x4(&e->TexTransform);

			ObjectConstants objConstants;
			XMStoreFloat4x4(&objConstants.World,XMMatrixTranspose(world));
			XMStoreFloat4x4(&objConstants.TexTransform,XMMatrixTranspose(texTransform));
			objConstants.DisplacementMapTexelSize = e->DisplacementMapTexelSize;
			objConstants.GridSpatialStep = e->GridSpatialStep;

			currObjectCB->CopyData(e->ObjCBIndex,objConstants);

			e->NumFramesDirty --;
		}
	}
}

void ShapesApp::UpdateMaterialCBs(const GameTimer& gt)
{
	auto currMaterialCB = mCurrFrameResource->MaterialCB.get();
	for(auto& e : Singleton<Dx2MaterialManager>::Instance()->GetMaterials())
	{
		Material* mat = e.second.get();
		if(mat->NumFramesDirty > 0)
		{
			XMMATRIX matTransform = XMLoadFloat4x4(&mat->MatTransform);

			MaterialConstants matConstants;
			matConstants.DiffuseAlbedo = mat->DiffuseAlbedo;
			matConstants.FresnelR0 = mat->FresnelR0;
			matConstants.Roughness = mat->Roughness;
			XMStoreFloat4x4(&matConstants.MatTransform,XMMatrixTranspose(matTransform));

			currMaterialCB->CopyData(mat->MatCBIndex, matConstants);

			// Next FrameResource need to be updated too.
			mat->NumFramesDirty--;
		}
	}
}

void ShapesApp::UpdateMainPassCB(const GameTimer& gt)
{
	XMMATRIX view = XMLoadFloat4x4(&mView);
	XMMATRIX proj = XMLoadFloat4x4(&mProj);

	XMMATRIX viewProj = XMMatrixMultiply(view, proj);
	
	auto matrixdeterview = XMMatrixDeterminant(view);
	auto matrixdeterproj = XMMatrixDeterminant(proj);
	auto matrixdeterviewProj = XMMatrixDeterminant(viewProj);
	
	XMMATRIX invView = XMMatrixInverse(&matrixdeterview, view);
	XMMATRIX invProj = XMMatrixInverse(&matrixdeterproj, proj);
	XMMATRIX invViewProj = XMMatrixInverse(&matrixdeterviewProj, viewProj);

	XMStoreFloat4x4(&mMainPassCB.View, XMMatrixTranspose(view));
	XMStoreFloat4x4(&mMainPassCB.InvView, XMMatrixTranspose(invView));
	XMStoreFloat4x4(&mMainPassCB.Proj, XMMatrixTranspose(proj));
	XMStoreFloat4x4(&mMainPassCB.InvProj, XMMatrixTranspose(invProj));
	XMStoreFloat4x4(&mMainPassCB.ViewProj, XMMatrixTranspose(viewProj));
	XMStoreFloat4x4(&mMainPassCB.InvViewProj, XMMatrixTranspose(invViewProj));
	mMainPassCB.EyePosW = mEyePos;
	mMainPassCB.RenderTargetSize = XMFLOAT2((float)mClientWidth, (float)mClientHeight);
	mMainPassCB.InvRenderTargetSize = XMFLOAT2(1.0f / mClientWidth, 1.0f / mClientHeight);
	mMainPassCB.NearZ = 1.0f;
	mMainPassCB.FarZ = 1000.0f;
	mMainPassCB.TotalTime = gt.TotalTime();
	mMainPassCB.DeltaTime = gt.DeltaTime();

	mMainPassCB.AmbientLight = { 0.25f, 0.25f, 0.35f, 1.0f };
	mMainPassCB.Lights[0].Direction = { 0.57735f, -0.57735f, 0.57735f };
	mMainPassCB.Lights[0].Strength = { 0.9f, 0.9f, 0.8f };
	mMainPassCB.Lights[1].Direction = { -0.57735f, -0.57735f, 0.57735f };
	mMainPassCB.Lights[1].Strength = { 0.3f, 0.3f, 0.3f };
	mMainPassCB.Lights[2].Direction = { 0.0f, -0.707f, -0.707f };
	mMainPassCB.Lights[2].Strength = { 0.15f, 0.15f, 0.15f };

	auto currPassCB = mCurrFrameResource->PassCB.get();
	currPassCB->CopyData(0, mMainPassCB);
}

void ShapesApp::UpdateReflectedPassCB(const GameTimer& gt)
{
	mReflectedPassCB = mMainPassCB;
	
	XMVECTOR mirrorPlane = XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f); // xy plane
	XMMATRIX R = XMMatrixReflect(mirrorPlane);

	for(int i = 0; i < 3; ++i)
	{
		XMVECTOR lightDir = XMLoadFloat3(&mMainPassCB.Lights[i].Direction);
		XMVECTOR reflectedLightDir = XMVector3TransformNormal(lightDir, R);
		XMStoreFloat3(&mReflectedPassCB.Lights[i].Direction, reflectedLightDir);
	}

	// Reflected pass stored in index 1
	auto currPassCB = mCurrFrameResource->PassCB.get();
	currPassCB->CopyData(1, mReflectedPassCB);
}

void ShapesApp::UpdateWavesGpu(const GameTimer& gt)
{
	static float t_base = 0.0f;
	if((mTimer.TotalTime() - t_base) >= 0.25f)
	{
		t_base += 0.25f;

		int i = MathHelper::Rand(4, mWaves->RowCount() - 5);
		int j = MathHelper::Rand(4, mWaves->ColumnCount() - 5);

		float r = MathHelper::RandF(0.2f, 0.5f);

		mWaves->Disturb(mCommandList.Get(),Singleton<Dx2RootSignatureManager>::Instance()->GetRootSignature(RootSignatureType::Wave).Get(),mPSOs["wavesDisturb"].Get(), i, j, r);
	}

	// Update the wave simulation.
	mWaves->Update(gt,mCommandList.Get(),Singleton<Dx2RootSignatureManager>::Instance()->GetRootSignature(RootSignatureType::Wave).Get(),mPSOs["wavesUpdate"].Get());
}

void ShapesApp::CreatePasses()
{
	auto basePass =  std::make_shared<BasePass>();
	basePass->SetCSWave(mWaves);
	
	auto alphaTestPass = std::make_shared<AlphaTestPass>();
	basePass->AddSubPass(alphaTestPass);
	
	auto wavesRenderPass = std::make_shared<WavesRenderPass>();
	wavesRenderPass->SetCSWave(mWaves);
	basePass->AddSubPass(wavesRenderPass);
	
	auto treespritePass = std::make_shared<TreeSpritesPass>();
	basePass->AddSubPass(treespritePass);
	
	auto stentilmirrorPass = std::make_shared<MirrorPass>();
	basePass->AddSubPass(stentilmirrorPass);
	
	auto reflectionPass = std::make_shared<ReflectionPass>();
	basePass->AddSubPass(reflectionPass);
	
	auto transparentPass = std::make_shared<TransparentPass>();
	basePass->AddSubPass(transparentPass);

	auto composePass = std::make_shared<ComposePass>();

	auto tessellationPass  = std::make_shared<TessellationPass>();

	//main graphics pass
	mAllPasses["basePass"] = basePass;
	mAllPasses["alphaTest"] = alphaTestPass;
	mAllPasses["wavesRender"] = wavesRenderPass;
	mAllPasses["treeSprites"] = treespritePass;
	mAllPasses["mirror"] = stentilmirrorPass;
	mAllPasses["reflection"] = reflectionPass;
	mAllPasses["transparent"] = transparentPass;
	mAllPasses["tessellation"] = tessellationPass;

	//post pass
	mAllPasses["compose"] = composePass;

	mMainPasses["basePass"] = basePass;
	mMainPasses["tessellation"] = tessellationPass;
}

void ShapesApp::InitialPasses()
{
	UINT ObjIndex = 0;
	for(auto it = mAllPasses.begin(); it != mAllPasses.end(); it++)
	{
		auto curPasses = it->second;
		curPasses->BuildRenderItems(mAllRenderItems,ObjIndex);
		curPasses->BuildPSOs(md3dDevice.Get(),mBackBufferFormat,mDepthStencilFormat,m4xMsaaState,m4xMsaaQuality);
	}

	//set update
	BasePass* basePass = static_cast<BasePass*>(mAllPasses["basePass"].get());
	if(basePass != nullptr)
	{
		mSkullRitem = basePass->GetSkullRitem();
	}
	
	ReflectionPass* reflectionPass = static_cast<ReflectionPass*>(mAllPasses["reflection"].get());
	if(reflectionPass != nullptr)
	{
		mReflectedSkullRitem = reflectionPass->GetReflectedSkullRitem();
	}

	TessellationPass* tessellationPass = static_cast<TessellationPass*>(mAllPasses["tessellation"].get());
	if(tessellationPass != nullptr)
	{
		mTessellationRitem = tessellationPass->GetTessellationRitem();
	}
}

void ShapesApp::LoadTextures()
{	
	Singleton<Dx2TextureManager>::Instance()->LoadTexture(md3dDevice.Get(),mCommandList.Get());
}

void ShapesApp::BuildDescriptorHeaps()
{
	auto otherSrvCount = 6;
	auto waveSrvCount = mWaves->DescriptorCount();
	auto blurSrvCount = mBlurFilter->DescriptorCount();
	auto offscreenSrvCount = mOffscreenRT->DescriptorCount();

	auto totalCount = otherSrvCount + waveSrvCount + blurSrvCount + offscreenSrvCount;
	
	D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = {};
	srvHeapDesc.NumDescriptors = totalCount;
	srvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
	srvHeapDesc.Flags =D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
	ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&srvHeapDesc,IID_PPV_ARGS(&mSrvDescriptorHeap)));

	CD3DX12_CPU_DESCRIPTOR_HANDLE hDescriptor(mSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());

	//texture heap
	Singleton<Dx2TextureManager>::Instance()->BuildDescriptorHeap(md3dDevice.Get(),hDescriptor,mCbvSrvDescriptorSize);
	
	auto waveOtherCount = otherSrvCount;
	mWaves->BuildDescriptors(
		CD3DX12_CPU_DESCRIPTOR_HANDLE(mSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart(),waveOtherCount,mCbvSrvDescriptorSize),
		CD3DX12_GPU_DESCRIPTOR_HANDLE(mSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart(),waveOtherCount,mCbvSrvDescriptorSize),
		mCbvSrvDescriptorSize
		);
	
	auto blurOtherCount = otherSrvCount + waveSrvCount;
	mBlurFilter->BuildDescriptors(
		CD3DX12_CPU_DESCRIPTOR_HANDLE(mSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart(),blurOtherCount,mCbvSrvDescriptorSize),
		CD3DX12_GPU_DESCRIPTOR_HANDLE(mSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart(),blurOtherCount,mCbvSrvDescriptorSize),
		mCbvSrvDescriptorSize
	);
	
	auto offscreenOthercount = otherSrvCount + waveSrvCount + blurSrvCount;
	auto offsetScreenRtvcount = 2;
	
	mOffscreenRT->BuildDescriptors(
		CD3DX12_CPU_DESCRIPTOR_HANDLE(mSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart(),offscreenOthercount,mCbvSrvDescriptorSize),
		CD3DX12_GPU_DESCRIPTOR_HANDLE(mSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart(),offscreenOthercount,mCbvSrvDescriptorSize),
		CD3DX12_CPU_DESCRIPTOR_HANDLE(mRtvHeap->GetCPUDescriptorHandleForHeapStart(),offsetScreenRtvcount,mRtvDescriptorSize)
		);
}

void ShapesApp::BuildShadersAndInputLayout()
{	
	Singleton<Dx2ShaderAndLayoutManager>::Instance()->BuildShaderAndLayout();
}

void ShapesApp::BuildMaterials()
{
	Singleton<Dx2MaterialManager>::Instance()->BuildMaterial();
}

void ShapesApp::BuildGeometry()
{
	Singleton<Dx2GeometryManager>::Instance()->BuildGeometry(md3dDevice.Get(),mCommandList.Get());
	Singleton<Dx2GeometryManager>::Instance()->BuildWavesGeometry(md3dDevice.Get(),mCommandList.Get(),mWaves->RowCount(),mWaves->ColumnCount(),mWaves->VertexCount());
}

void ShapesApp::BuildRootSignature()
{
	Singleton<Dx2RootSignatureManager>::Instance()->BuildRootSignature(md3dDevice.Get());
}

void ShapesApp::BuildCSPSOs()
{
	/////////
	///
	//wave disturb
	D3D12_COMPUTE_PIPELINE_STATE_DESC wavesDisturbPSO = {};
	wavesDisturbPSO.pRootSignature = Singleton<Dx2RootSignatureManager>::Instance()->GetRootSignature(RootSignatureType::Wave).Get();
	wavesDisturbPSO.CS = 
	{
		reinterpret_cast<BYTE*>(Singleton<Dx2ShaderAndLayoutManager>::Instance()->GetShader("wavesDisturbCS")->GetBufferPointer()),
		Singleton<Dx2ShaderAndLayoutManager>::Instance()->GetShader("wavesDisturbCS")->GetBufferSize()
	};
	wavesDisturbPSO.Flags = D3D12_PIPELINE_STATE_FLAG_NONE;
	ThrowIfFailed(md3dDevice->CreateComputePipelineState(&wavesDisturbPSO,IID_PPV_ARGS(&mPSOs["wavesDisturb"])));

	//wave update
	D3D12_COMPUTE_PIPELINE_STATE_DESC wavesUpdatePSO = {};
	wavesUpdatePSO.pRootSignature = Singleton<Dx2RootSignatureManager>::Instance()->GetRootSignature(RootSignatureType::Wave).Get();
	wavesUpdatePSO.CS = 
	{
		reinterpret_cast<BYTE*>(Singleton<Dx2ShaderAndLayoutManager>::Instance()->GetShader("wavesUpdateCS")->GetBufferPointer()),
		Singleton<Dx2ShaderAndLayoutManager>::Instance()->GetShader("wavesUpdateCS")->GetBufferSize()
	};
	wavesUpdatePSO.Flags = D3D12_PIPELINE_STATE_FLAG_NONE;
	ThrowIfFailed(md3dDevice->CreateComputePipelineState(&wavesUpdatePSO,IID_PPV_ARGS(&mPSOs["wavesUpdate"])));

	//vec add
	D3D12_COMPUTE_PIPELINE_STATE_DESC vecAddPSO = {};
	vecAddPSO.pRootSignature = Singleton<Dx2RootSignatureManager>::Instance()->GetRootSignature(RootSignatureType::VecAndAdd).Get();
	vecAddPSO.CS =
	{
		reinterpret_cast<BYTE*>(Singleton<Dx2ShaderAndLayoutManager>::Instance()->GetShader("vecAddCS")->GetBufferPointer()),
		Singleton<Dx2ShaderAndLayoutManager>::Instance()->GetShader("vecAddCS")->GetBufferSize()
	};
	vecAddPSO.Flags = D3D12_PIPELINE_STATE_FLAG_NONE;
	ThrowIfFailed(md3dDevice->CreateComputePipelineState(&vecAddPSO,IID_PPV_ARGS(&mPSOs["vecAdd"])));

	D3D12_COMPUTE_PIPELINE_STATE_DESC horzBlurPSO = {};
	horzBlurPSO.pRootSignature = Singleton<Dx2RootSignatureManager>::Instance()->GetRootSignature(RootSignatureType::Blur).Get();
	horzBlurPSO.CS =
	{
		reinterpret_cast<BYTE*>(Singleton<Dx2ShaderAndLayoutManager>::Instance()->GetShader("horzBlurCS")->GetBufferPointer()),
		Singleton<Dx2ShaderAndLayoutManager>::Instance()->GetShader("horzBlurCS")->GetBufferSize()
	};
	horzBlurPSO.Flags = D3D12_PIPELINE_STATE_FLAG_NONE;
	ThrowIfFailed(md3dDevice->CreateComputePipelineState(&horzBlurPSO,IID_PPV_ARGS(&mPSOs["horzBlur"])));

	D3D12_COMPUTE_PIPELINE_STATE_DESC vertBlurPSO = {};
	vertBlurPSO.pRootSignature = Singleton<Dx2RootSignatureManager>::Instance()->GetRootSignature(RootSignatureType::Blur).Get();
	vertBlurPSO.CS =
	{
		reinterpret_cast<BYTE*>(Singleton<Dx2ShaderAndLayoutManager>::Instance()->GetShader("vertBlurCS")->GetBufferPointer()),
		Singleton<Dx2ShaderAndLayoutManager>::Instance()->GetShader("vertBlurCS")->GetBufferSize()
	};
	vertBlurPSO.Flags = D3D12_PIPELINE_STATE_FLAG_NONE;
	ThrowIfFailed(md3dDevice->CreateComputePipelineState(&vertBlurPSO,IID_PPV_ARGS(&mPSOs["vertBlur"])));
	
}
void ShapesApp::BuildFrameResources()
{
	for(int i = 0; i < gNumFrameResources; ++i )
	{
		mFrameResources.push_back(std::make_unique<FrameResource>(md3dDevice.Get(),
			2,(UINT)mAllRenderItems.size(), (UINT)Singleton<Dx2MaterialManager>::Instance()->GetMaterials().size(),mWaves->VertexCount()));
	}
}


